package com.huayi.datagovernance.utils.bean;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.bean.copier.ValueProvider;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.map.MapUtil;
import org.junit.Assert;
import org.junit.Test;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Bean工具-BeanUtil
 * @author wuch
 * @version 1.0
 * @date 2021/4/19 13:08
 */
public class BeanUtilDemo {
    /*什么是Bean
      把一个拥有对属性进行set和get方法的类，我们就可以称之为JavaBean。实际上JavaBean就是一个Java类，在这个Java类中就默认形成了一种规则——对属性进行设置和获得。
      而反之将说Java类就是一个JavaBean，这种说法是错误的，因为一个java类中不一定有对属性的设置和获得的方法（也就是不一定有set和get方法）。
      通常Java中对Bean的定义是包含setXXX和getXXX方法的对象，在Hutool中，采取一种简单的判定Bean的方法：是否存在只有一个参数的setXXX方法。
      Bean工具类主要是针对这些setXXX和getXXX方法进行操作，比如将Bean对象转为Map等等。*/


    /**
     * 是否为Bean对象
     * BeanUtil.isBean方法根据是否存在只有一个参数的setXXX方法或者public类型的字段来判定是否是一个Bean对象。
     * 这样的判定方法主要目的是保证至少有一个setXXX方法用于属性注入。
     */
    @Test
    public void test1(){
        boolean isBean = BeanUtil.isBean(HashMap.class);//false
        Assert.assertSame(true, isBean);
    }


   /* 内省 Introspector
    把一类中需要进行设置和获得的属性访问权限设置为private（私有的）让外部的使用者看不见摸不着，
    而通过public（共有的）set和get方法来对其属性的值来进行设置和获得，而内部的操作具体是怎样的？
    外界使用的人不用知道，这就称为内省。
    Hutool中对内省的封装包括：
    */

    /**
     * 1. BeanUtil.	getPropertyDescriptor(Class<?> clazz, String fieldName) 获得Bean类属性描述，大小写敏感
     * 2. BeanUtil.getPropertyDescriptors(Class<?> clazz) 获得Bean字段描述数组
     * 3. BeanUtil.getPropertyDescriptorMap(Class<?> clazz, boolean ignoreCase)  获得字段名和字段描述Map，获得的结果会缓存在 BeanInfoCache中 ignoreCase 是否忽略大小写
     * 4. BeanUtil.getPropertyDescriptor(Class<?> clazz, String fieldName, boolean ignoreCase) 获得Bean类指定属性描述
     */
    @Test
    public void test2(){
//        1. BeanUtil.	getPropertyDescriptor(Class<?> clazz, String fieldName)
        PropertyDescriptor propertyDescriptor = BeanUtil.getPropertyDescriptor(SubPerson.class,"subName");
        Console.log(propertyDescriptor.getReadMethod());
//        2. BeanUtil.getPropertyDescriptors(Class<?> clazz)
        PropertyDescriptor[] propertyDescriptors = BeanUtil.getPropertyDescriptors(SubPerson.class);
        for (PropertyDescriptor pd: propertyDescriptors){
            //打印set方法，输出结果如下：
//            public void com.huayi.datagovernance.utils.bean.IntrosBean.setId(java.lang.String)
//            public void com.huayi.datagovernance.utils.bean.IntrosBean.setName(java.lang.String)
            System.out.println(pd.getWriteMethod());
        }
//        3. BeanUtil.getPropertyDescriptorMap(Class<?> clazz, boolean ignoreCase)
        Map<String,PropertyDescriptor> propertyDescriptorMap = BeanUtil.getPropertyDescriptorMap(SubPerson.class, false);
        System.out.println(propertyDescriptorMap);
//        4. BeanUtil.getPropertyDescriptor(Class<?> clazz, String fieldName, boolean ignoreCase)
        BeanUtil.getPropertyDescriptor(SubPerson.class, "subName",true);
        System.out.println(propertyDescriptorMap);

    }

    /**
     * Bean属性注入
     * BeanUtil.fillBean方法是bean注入的核心方法，此方法传入一个ValueProvider接口，通过实现此接口来获得key对应的值。CopyOptions参数则提供一些注入属性的选项。
     * CopyOptions的配置项包括：
     *
     * editable 限制的类或接口，必须为目标对象的实现接口或父类，用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将editable设置为父类。
     * ignoreNullValue 是否忽略空值，当源对象的值为null时，true: 忽略而不注入此值，false: 注入null
     * ignoreProperties 忽略的属性列表，设置一个属性列表，不拷贝这些属性值
     * ignoreError 是否忽略字段注入错误
     * 可以通过CopyOptions.create()方法创建一个默认的配置项，通过setXXX方法设置每个配置项。
     *
     * ValueProvider接口需要实现两个方法：
     *
     * value方法是通过key和目标类型来从任何地方获取一个值，并转换为目标类型，如果返回值不和目标类型匹配，将会自动调用Convert.convert方法转换。
     * containsKey方法主要是检测是否包含指定的key，如果不包含这个key，其对应的属性将会忽略注入。
     */
    @Test
    public void test3(){
        //首先定义两个bean： Person  SubPerson
        //然后注入这个bean：
        Person person = BeanUtil.fillBean(new Person(), new ValueProvider<String>(){
            //方法是通过key和目标类型来从任何地方获取一个值，并转换为目标类型，如果返回值不和目标类型匹配，将会自动调用Convert.convert方法转换
            @Override
            public Object value(String key, Type valueType) {
                switch (key) {
                    case "name":
                        return "张三";
                    case "age":
                        return 18;
                }
                return null;
            }

            //方法主要是检测是否包含指定的key，如果不包含这个key，其对应的属性将会忽略注入。
            @Override
            public boolean containsKey(String key) {
                //总是存在key
                return true;
            }

        }, CopyOptions.create());

        Assert.assertEquals(person.getName(), "张三");
        Assert.assertEquals(person.getAge(), 18);

/*        同时，Hutool还提供了BeanUtil.toBean方法，此处并不是传Bean对象，而是Bean类，Hutool会自动调用默认构造方法创建对象。
        基于BeanUtil.fillBean方法Hutool还提供了Map对象键值对注入Bean，其方法有：*/
//        BeanUtil.fillBeanWithMap 使用Map填充bean
        HashMap<String, Object> map = CollUtil.newHashMap();
        map.put("name", "Joe");
        map.put("age", 12);
        map.put("subName", "迪迦");
        map.put("openId", "DFDFSDFWERWER");
        SubPerson person1 = BeanUtil.fillBeanWithMap(map, new SubPerson(), false);
        Console.log(person1);

//        BeanUtil.fillBeanWithMapIgnoreCase 使用Map填充bean，忽略大小写
        HashMap<String, Object> map2 = CollUtil.newHashMap();
        map.put("Name", "Joe");
        map.put("aGe", 12);
        map.put("SUBNAME", "泰羅");
        map.put("openId", "DFDFSDFWERWER");
        SubPerson person2 = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false);
        Console.log(person2);


    }

    /**
     * Map转Bean
     * 同时提供了map转bean的方法，与fillBean不同的是，此处并不是传Bean对象，而是Bean类，
     * Hutool会自动调用默认构造方法创建对象。当然，前提是Bean类有默认构造方法（空构造），这些方法有：
     */
    @Test
    public void test4(){
        // BeanUtil.mapToBean(Map<?,?> map, Class<T> beanClass, boolean isToCamelCase, CopyOptions copyOptions) isToCamelCase:是否将map中的下划线风格key转换为驼峰风格
        HashMap<String, Object> map = CollUtil.newHashMap();
        map.put("a_name", "Joe");
        map.put("b_age", 12);
        map.put("bg_color", "green");
        // key和字段不一致时，要手动设置映射关系 别名，用于对应bean的字段名
        HashMap<String, String> mapping = CollUtil.newHashMap();
        mapping.put("a_name", "name");
        mapping.put("b_age", "age");
        Person person = BeanUtil.mapToBean(map, Person.class, false, CopyOptions.create().setFieldMapping(mapping));
        Console.log(person);

        // BeanUtil.mapToBeanIgnoreCase
        HashMap<String, Object> map1 = CollUtil.newHashMap();
        map1.put("Name", "Joe");
        map1.put("aGe", 13);
        map1.put("bg_color", "绿色");
        Person person1 = BeanUtil.mapToBeanIgnoreCase(map1, Person.class, false);
        Console.log(person1);

        // toBean(Object source, Class<T> clazz) 对象或Map转Bean
        MapBuilder<String, Object> builder = MapUtil.builder("name", Object.class);
        builder.put("name","牛鲨").put("age",25);
        Person person2 = BeanUtil.toBean(builder.build(), Person.class);
        Console.log(person2);//Person(name=牛鲨, age=25, bgColor=yellow)


    }

    /**
     * Bean转为Map
     */
    @Test
    public void test5(){
        // beanToMap(Object bean) 对象转Map，不进行驼峰转下划线，不忽略值为空的字段
        SubPerson person = new SubPerson();
        person.setAge(14);
        person.setName("测试A11");
        person.setSubName("sub名字");
        person.setBgColor("七彩");

        Map<String, Object> map = BeanUtil.beanToMap(person);
        Console.log(map);//{id=null, subName=sub名字, isSlow=null, name=测试A11, age=14, bgColor=七彩}

        // beanToMap(Object bean, boolean isToUnderlineCase, boolean ignoreNullValue) 对象转Map
        Map<String, Object> map1 = BeanUtil.beanToMap(person, true, true);
        Console.log(map1);//{sub_name=sub名字, name=测试A11, age=14, bg_color=七彩}

        // beanToMap(Object bean, Map<String,Object> targetMap, boolean isToUnderlineCase, boolean ignoreNullValue) 对象转到指定的Map
        HashMap<String, Object> targetMap = MapUtil.newHashMap();
        Map<String, Object> map2 = BeanUtil.beanToMap(person, targetMap, true, false);
        Console.log(targetMap);//{bg_color=七彩, sub_name=sub名字, name=测试A11, is_slow=null, id=null, age=14}
    }

    /**
     * Bean转Bean
     * Bean之间的转换主要是相同属性的复制，因此方法名为copyProperties，此方法支持Bean和Map之间的字段复制
     * BeanUtil.copyProperties方法同样提供一个CopyOptions参数用于自定义属性复制。。
     */
    @Test
    public void test6(){
        //copyProperties(Object source, Object target, CopyOptions copyOptions) 复制Bean对象属性
        //限制类用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将editable设置为父类
        SubPerson p1 = new SubPerson();
        p1.setIsSlow(true);
        p1.setName("测试");
        p1.setSubName("sub测试");

        Map<String, Object> map = MapUtil.newHashMap();
        BeanUtil.copyProperties(p1, map);
        Console.log(map);//{isSlow=true, bgColor=yellow, subName=sub测试, name=测试, id=null, age=41}

        //	copyProperties(Object source, Object target, String... ignoreProperties) 复制Bean对象属性 ignoreProperties 忽略的字段
        Map<String, Object> map1 = MapUtil.newHashMap();
        map1.put("subname","如果的事");
        BeanUtil.copyProperties(p1, map1, "id");
        Console.log(map1);//{isSlow=true, bgColor=yellow, subName=sub测试, subname=如果的事, name=测试, age=41}

        //通过反射设置属性
        BeanUtil.setFieldValue(map1, "name", "你打野");
        Console.log(BeanUtil.getFieldValue(map1, "name"));//你打野
    }

    /**
     * Alias注解
     */
    @Test
    public void test7(){
//        5.x的Hutool中增加了一个自定义注解：Alias，通过此注解可以给Bean的字段设置别名。

//        首先我们给Bean加上注解：
        // Lombok注解
        /*@Getter
        @Setter
        public static class SubPersonWithAlias {
            @Alias("aliasSubName")
            private String subName;
            private Boolean slow;
        }*/

        /*SubPersonWithAlias person = new SubPersonWithAlias();
        person.setSubName("sub名字");
        person.setSlow(true);

        // Bean转换为Map时，自动将subName修改为aliasSubName
        Map<String, Object> map = BeanUtil.beanToMap(person);
        // 返回"sub名字"
        map.get("aliasSubName");*/

//        同样Alias注解支持注入Bean时的别名：
        /*Map<String, Object> map = MapUtil.newHashMap();
        map.put("aliasSubName", "sub名字");
        map.put("slow", true);*/
        /*SubPersonWithAlias subPersonWithAlias = BeanUtil.mapToBean(map, SubPersonWithAlias.class, false);
        // 返回"sub名字"
        subPersonWithAlias.getSubName();*/
    }
}
